<?php
/**
 * Created by PhpStorm.
 * User: 徐州
 * Date: 2017/5/4
 * Time: 18:01
 *
 * @link http://www.open-open.com/lib/view/open1355830836135.html
 */

namespace App\App\Services;

use App\Contracts\Repositories\ActivityModelRepository;
use App\Contracts\Repositories\ActivityRepository;
use App\Events\ActivityPaused;
use App\Models\Activity\Activity;
use App\Models\ActivityModel\ActivityModel;
use App\Models\VoteCandidate\VoteCandidate;
use App\Models\VoteCandidate\VoteCandidateService;
use App\Models\VoteGroup\VoteGroupService;
use App\Models\VoteRecord\VoteRecord;
use App\Models\VoteRecord\VoteRecordService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;

class SyncCacheService
{
    private $voteGroupService;
    private $voteCandidateService;
    private $voteRecordService;
    private $appDebug;
    private $activityRepository;
    private $activityModelRepository;

    public function __construct(
        VoteGroupService $voteGroupService,
        VoteCandidateService $voteCandidateService,
        VoteRecordService $voteRecordService,
        ActivityRepository $activityRepository,
        ActivityModelRepository $activityModelRepository
    )
    {
        $this->voteGroupService = $voteGroupService;
        $this->voteCandidateService = $voteCandidateService;
        $this->voteRecordService = $voteRecordService;
        $this->activityRepository = $activityRepository;
        $this->activityModelRepository = $activityModelRepository;
        $this->appDebug = app()['config']['debug'];
    }

    /**
     * 改变票数
     *
     * @param $activityID
     * @param $itemID
     * @param $number
     *
     * @return mixed
     */
    public function changeRanking($activityID, $itemID, $number)
    {
        return Redis::zincrby('ranking:' . $activityID, $number, $itemID);//返回改变后的数量
    }

    public function changeScore($activityID, $itemID, $number)
    {
        return Redis::hincrby('scores:' . $activityID, $itemID, $number);
    }

    /**
     * 计算投票排行
     *
     * @param        $activityID
     * @param string $itemID
     *
     * @return array|int|mixed
     */
    private function rank($activityID, $itemID)
    {
        $rank = Redis::zrank('ranking:' . $activityID, $itemID);
        if ($rank) {
            return $rank + 1;
        } else {
            return 1;
        }
    }

    /**
     * 存储投票日志
     *
     * @return bool
     */
    public function storageVoteLogs()
    {
        $activityModel = $this->activityModelRepository->findByRule(ActivityModel::ACTIVITY_MODEL_VOTE);
        if (!$activityModel) {
            Log::debug(' Have no voting model' . PHP_EOL);

            return false;
        }
        $activities = $this->activityRepository->findRunningModel($activityModel->getId());
        if (empty($activities)) {
            Log::debug($activityModel->getRule() . ' have no activity' . PHP_EOL);

            return false;
        }
        $this->syncVoteLogs($activities);

        return true;
    }

    /**
     * 获取指定活动投票日志队列未取出数据条数
     *
     * @param Activity $activity
     * @param string   $date
     *
     * @return int
     */
    private function getQueueVoteLogsNumber(Activity $activity, string $date)
    {
        $voteNumber = Redis::llen('logs:' . $activity->getId() . ':' . $date);
        if ($voteNumber == 0 && $this->appDebug == 'true') {
            Log::debug('empty list: logs:' . $activity->getId() . ':' . $date);
        }

        return $voteNumber;
    }

    /**
     * 同步单条投票日志入库
     *
     * @param Activity $activity
     * @param string   $today
     *
     * @return bool
     */
    private function syncOneVoteLog(Activity $activity, string $today)
    {
        try {
            $voteLogJson = $voteLog = Redis::lpop('logs:' . $activity->getId() . ':' . $today);
            file_put_contents("/tmp/123", 'json:'.$voteLogJson.PHP_EOL, FILE_APPEND);
            $voteLog = json_decode($voteLog, true);
            if (!$voteLog) {
                return false;
            }
            $voteRecords = [
                'uid'          => md5($voteLog['user']),
                'candidate_id' => $voteLog['item'],
                'voted_at'     => intval($voteLog['vote_at']),
                'ip'           => $voteLog['ip'],
                'device_id' => Arr::get($voteLog, 'device_id', ''),
                'location' => Arr::get($voteLog, 'location', ''),
            ];
            if(!empty($voteRecords['candidate_id'])){
                DB::beginTransaction();
                VoteRecord::create($voteRecords);
                $voteCandidate = VoteCandidate::find($voteLog['item']);
                $voteCandidate->votes_number = $voteCandidate->votes_number + 1;
                $voteCandidate->save();
                DB::commit();
                if ($this->appDebug == 'true') {
                    Log::debug("保存成功：logs:" . $activity->getId() . ":" . $today . " " . $voteLogJson . PHP_EOL);
                }
            }
        } catch (\Exception $exception) {
            DB::rollback();
            Redis::lpush('logs:' . $activity->getId() . ':' . $today, $voteLogJson);
            Log::error($exception->getMessage() . "：logs:" . $activity->getId() . ":" . $today . " " . $voteLogJson . PHP_EOL);
        }

        return true;
    }

    /**
     * 同步投票日志入库
     *
     * @param Collection $activities
     *
     * @return bool
     */
    private function syncVoteLogs(Collection $activities)
    {
        $date = Carbon::now()->toDateString();
        $yesterday = Carbon::yesterday()->toDateString();
        foreach ($activities as $activity) {
            //check yesterday queue
            $number = $this->getQueueVoteLogsNumber($activity, $yesterday);
            if ($number > 0) {
                //prioritize yesterday's queue
                $date = $yesterday;
            }
            $number = $this->getQueueVoteLogsNumber($activity, $date);
            if ($number == 0) {
                continue;
            }
            for ($i = 0; $i < $number; $i++) {
                $ret = $this->syncOneVoteLog($activity, $date);
                if (!$ret) {
                    break;
                }
            }
        }

        return true;
    }

    public function updateCacheItem($resourceID)
    {
        // $voteCandidate = $this->voteCandidateService->getByResourceUrl($resourceID);
        $voteCandidate = $this->voteCandidateService->getByResourceId($resourceID);
        $voteCandidateArray = $voteCandidate->toArray();
        $activityIds = [];
        if (!empty($voteCandidateArray)) {
            foreach ($voteCandidateArray as $item) {
                $this->cacheItems($item);
                $activityIds[] = $item['activity_id'];
            }

            $activityIds = array_unique($activityIds);

            foreach ($activityIds as $id) {
                event(new ActivityPaused($id));
            }
        }
    }

    private function cacheItems($voteCandidate)
    {
        $resourceUrls = [];
        if (!empty($voteCandidate['resource'])) {
            $resourceUrls = array_column($voteCandidate['resource'], 'resource_url');
        }
        //准备投票项数据
        $item = [
            'group' => $voteCandidate['group_id'],
            'name'  => $voteCandidate['title'],
            'votes' => $voteCandidate['votes_number'],
            'cover' => $voteCandidate['cover_url'],
            'src'   => $resourceUrls,
            'type'  => $voteCandidate['type'],
            'memo'  => $voteCandidate['content'],
            'rank'  => $this->rank($voteCandidate['activity_id'], $voteCandidate['id']),
        ];
        try {
            Redis::set('items:' . $voteCandidate['activity_id'] . ':' . $voteCandidate['id'], json_encode($item));
            if ($this->appDebug == 'true') {
                Log::debug('准备数据items:' . $voteCandidate['id']);
                echo "准备数据成功：items:" . $voteCandidate['id'] . PHP_EOL;
            }
        } catch (\Exception $exception) {
            Log::error($exception->getMessage());
            echo $exception->getMessage() . "：items:" . $voteCandidate['id'] . PHP_EOL;
        }
    }

    private function cacheVotes($voteCandidate)
    {
        $resourceUrls = [];
        if (!empty($voteCandidate['resource'])) {
            $resourceUrls = array_column($voteCandidate['resource'], 'resource_url');
        }
        //准备投票活动数据
        $items = [
            $voteCandidate['vote_group']['id'] => [
                $voteCandidate['id'] => [
                    'name'    => $voteCandidate['title'],
                    'type'    => $voteCandidate['type'],
                    'src'     => $resourceUrls,
                    'cover'   => $voteCandidate['cover_url'],
                    'content' => $voteCandidate['content'],
                ],
            ],
        ];
        $activityRule = json_decode($voteCandidate['activity']['limit'], true);
        $activities = [
            'title'       => $voteCandidate['activity']['name'],
            'start_time'  => $voteCandidate['activity']['startTime'],
            'end_time'    => $voteCandidate['activity']['endTime'],
            'allow_votes' => $activityRule['num'],
            'brush_mode'  => $activityRule['type'] == 'day' ? 1 : 2,
            'rules'       => $voteCandidate['activity']['rule'],
            'items'       => $items,
        ];
        try {
            Redis::set('vote:' . $voteCandidate['activity_id'], json_encode($activities));
            if ($this->appDebug == 'true') {
                Log::debug('准备数据vote:' . $voteCandidate['activity_id']);
                echo "准备数据成功：vote:" . $voteCandidate['activity_id'] . PHP_EOL;
            }
        } catch (\Exception $exception) {
            Log::error($exception->getMessage());
            echo $exception->getMessage() . "：vote:" . $voteCandidate['activity_id'] . PHP_EOL;
        }
    }

    private function cacheSearchs($voteCandidate)
    {
        try {
            Redis::hset('search:' . $voteCandidate['activity_id'], md5($voteCandidate['title']), $voteCandidate['id']);
            if ($this->appDebug == 'true') {
                Log::debug('准备数据search:' . $voteCandidate['activity_id']);
                echo "准备数据成功：准备数据search:" . $voteCandidate['activity_id'] . PHP_EOL;
            }
        } catch (\Exception $exception) {
            Log::error($exception->getMessage());
            echo $exception->getMessage() . "：vote:" . $voteCandidate['activity_id'] . PHP_EOL;
        }
    }

    public function cache()
    {
        $voteCandidates = $this->voteCandidateService->getAll();
        $voteCandidates = $voteCandidates->toArray();
        if (empty($voteCandidates)) {
            return;
        }
        //把数据库数据写入缓存
        foreach ($voteCandidates as $voteCandidate) {
            $this->cacheItems($voteCandidate);
            $this->cacheVotes($voteCandidate);
            $this->cacheSearchs($voteCandidate);
        }
    }
}
